/* ==========================================
 * JGraphT : a free Java graph-theory library
 * ==========================================
 *
 * Project Info:  http://jgrapht.sourceforge.net/
 * Project Creator:  Barak Naveh (http://sourceforge.net/users/barak_naveh)
 *
 * (C) Copyright 2003-2008, by Barak Naveh and Contributors.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */
/* ---------------------------
 * DefaultListenableGraph.java
 * ---------------------------
 * (C) Copyright 2003-2008, by Barak Naveh and Contributors.
 *
 * Original Author:  Barak Naveh
 * Contributor(s):   Christian Hammer
 *
 * $Id: DefaultListenableGraph.java 1258 2013-04-18 20:27:18Z freecode4susant $
 *
 * Changes
 * -------
 * 24-Jul-2003 : Initial revision (BN);
 * 04-Aug-2003 : Strong refs to listeners instead of weak refs (BN);
 * 10-Aug-2003 : Adaptation to new event model (BN);
 * 07-Mar-2004 : Fixed unnecessary clone bug #819075 (BN);
 * 11-Mar-2004 : Made generic (CH);
 * 07-May-2006 : Changed from List<Edge> to Set<Edge> (JVS);
 * 28-May-2006 : Moved connectivity info from edge to graph (JVS);
 *
 */

package org.jgrapht.graph;

import org.jgrapht.Graph;
import org.jgrapht.ListenableGraph;
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.event.GraphListener;
import org.jgrapht.event.GraphVertexChangeEvent;
import org.jgrapht.event.VertexSetListener;
import org.jgrapht.util.TypeUtil;

import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import java.util.Set;

/**
 * A graph backed by the the graph specified at the constructor, which can be
 * listened by <code>GraphListener</code> s and by <code>
 * VertexSetListener</code> s. Operations on this graph "pass through" to the to
 * the backing graph. Any modification made to this graph or the backing graph
 * is reflected by the other.
 *
 * <p>This graph does <i>not</i> pass the hashCode and equals operations through
 * to the backing graph, but relies on <tt>Object</tt>'s <tt>equals</tt> and
 * <tt>hashCode</tt> methods.</p>
 *
 * @author Barak Naveh
 * @see GraphListener
 * @see VertexSetListener
 * @since Jul 20, 2003
 * @param <V>
 * @param <E>
 */
public class DefaultListenableGraph<V, E> extends GraphDelegator<V, E>
        implements ListenableGraph<V, E>, Cloneable {

    private static final long serialVersionUID                 = 3977575900898471984L;
    private ArrayList<GraphListener<V, E>> graphListeners      = new ArrayList<>();
    private ArrayList<VertexSetListener<V>> vertexSetListeners = new ArrayList<>();
    private final FlyweightEdgeEvent<V, E> reuseableEdgeEvent;
    private final FlyweightVertexEvent<V> reuseableVertexEvent;
    private boolean reuseEvents;

    /**
     * Creates a new listenable graph.
     *
     * @param g the backing graph.
     */
    public DefaultListenableGraph(final Graph<V, E> g) {
        this(g, false);
    }

    /**
     * Creates a new listenable graph. If the <code>reuseEvents</code> flag is
     * set to <code>true</code> this class will reuse previously fired events
     * and will not create a new object for each event. This option increases
     * performance but should be used with care, especially in multithreaded
     * environment.
     *
     * @param g the backing graph.
     * @param reuseEvents whether to reuse previously fired event objects
     * instead of creating a new event object for each event.
     *
     * @throws IllegalArgumentException if the backing graph is already a
     * listenable graph.
     */
    public DefaultListenableGraph(final Graph<V, E> g, final boolean reuseEvents) {

        super(g);

        this.reuseEvents     = reuseEvents;
        reuseableEdgeEvent   = new FlyweightEdgeEvent<>(this, -1, null);
        reuseableVertexEvent = new FlyweightVertexEvent<>(this, -1, null);


        // the following restriction could be probably relaxed in the future.
        if (g instanceof ListenableGraph<?, ?>) {
            throw new IllegalArgumentException("base graph cannot be listenable");
        }
    }

    /**
     * If the <code>reuseEvents</code> flag is set to <code>true</code> this
     * class will reuse previously fired events and will not create a new object
     * for each event. This option increases performance but should be used with
     * care, especially in multithreaded environment.
     *
     * @param reuseEvents whether to reuse previously fired event objects
     * instead of creating a new event object for each event.
     */
    public void setReuseEvents(final boolean reuseEvents) {
        this.reuseEvents = reuseEvents;
    }

    /**
     * Tests whether the <code>reuseEvents</code> flag is set. If the flag is
     * set to <code>true</code> this class will reuse previously fired events
     * and will not create a new object for each event. This option increases
     * performance but should be used with care, especially in multithreaded
     * environment.
     *
     * @return the value of the <code>reuseEvents</code> flag.
     */
    public boolean isReuseEvents() {
        return reuseEvents;
    }

    /**
     * @see Graph#addEdge(Object, Object)
     */
    public E addEdge(final V sourceVertex, final V targetVertex) {

        final E e = super.addEdge(sourceVertex, targetVertex);

        if (e != null) {
            fireEdgeAdded(e);
        }

        return e;
    }

    /**
     * @see Graph#addEdge(Object, Object, Object)
     */
    public boolean addEdge(final V sourceVertex, final V targetVertex, final E e) {

        final boolean added = super.addEdge(sourceVertex, targetVertex, e);

        if (added) {
            fireEdgeAdded(e);
        }

        return added;
    }

    /**
     * @see ListenableGraph#addGraphListener(GraphListener)
     */
    public void addGraphListener(final GraphListener<V, E> l) {
        addToListenerList(graphListeners, l);
    }

    /**
     * @see Graph#addVertex(Object)
     */
    public boolean addVertex(final V v) {

        final boolean modified = super.addVertex(v);

        if (modified) {
            fireVertexAdded(v);
        }

        return modified;
    }

    /**
     * @see ListenableGraph#addVertexSetListener(VertexSetListener)
     */
    public void addVertexSetListener(final VertexSetListener<V> l) {
        addToListenerList(vertexSetListeners, l);
    }

    /**
     * @see java.lang.Object#clone()
     */
    public Object clone() {

        try {

            final TypeUtil<DefaultListenableGraph<V, E>> typeDecl = null;
            final DefaultListenableGraph<V, E> g = TypeUtil.uncheckedCast(super.clone(), typeDecl);

            g.graphListeners     = new ArrayList<>();
            g.vertexSetListeners = new ArrayList<>();

            return g;

        } catch (CloneNotSupportedException e) {


            // should never get here since we're Cloneable
            e.printStackTrace();

            throw new RuntimeException("internal error");
        }
    }

    /**
     * @see Graph#removeEdge(Object, Object)
     */
    public E removeEdge(final V sourceVertex, final V targetVertex) {

        final E e = super.removeEdge(sourceVertex, targetVertex);

        if (e != null) {
            fireEdgeRemoved(e);
        }

        return e;
    }

    /**
     * @see Graph#removeEdge(Object)
     */
    public boolean removeEdge(final E e) {

        final boolean modified = super.removeEdge(e);

        if (modified) {
            fireEdgeRemoved(e);
        }

        return modified;
    }

    /**
     * @see ListenableGraph#removeGraphListener(GraphListener)
     */
    public void removeGraphListener(final GraphListener<V, E> l) {
        graphListeners.remove(l);
    }

    /**
     * @see Graph#removeVertex(Object)
     */
    public boolean removeVertex(final V v) {

        if (containsVertex(v)) {

            final Set<E> touchingEdgesList = edgesOf(v);


            // copy set to avoid ConcurrentModificationException
            removeAllEdges(new ArrayList<>(touchingEdgesList));
            super.removeVertex(v);    // remove the vertex itself
            fireVertexRemoved(v);

            return true;

        } else {
            return false;
        }
    }

    /**
     * @see ListenableGraph#removeVertexSetListener(VertexSetListener)
     */
    public void removeVertexSetListener(final VertexSetListener<V> l) {
        vertexSetListeners.remove(l);
    }

    /**
     * Notify listeners that the specified edge was added.
     *
     * @param edge the edge that was added.
     */
    protected void fireEdgeAdded(final E edge) {

        final GraphEdgeChangeEvent<V, E> e =
            createGraphEdgeChangeEvent(GraphEdgeChangeEvent.EDGE_ADDED, edge);

        for (final GraphListener<V, E> l : graphListeners) {
            l.edgeAdded(e);
        }
    }

    /**
     * Notify listeners that the specified edge was removed.
     *
     * @param edge the edge that was removed.
     */
    protected void fireEdgeRemoved(final E edge) {

        final GraphEdgeChangeEvent<V, E> e =
            createGraphEdgeChangeEvent(GraphEdgeChangeEvent.EDGE_REMOVED, edge);

        for (final GraphListener<V, E> l : graphListeners) {
            l.edgeRemoved(e);
        }
    }

    /**
     * Notify listeners that the specified vertex was added.
     *
     * @param vertex the vertex that was added.
     */
    protected void fireVertexAdded(final V vertex) {

        final GraphVertexChangeEvent<V> e =
            createGraphVertexChangeEvent(GraphVertexChangeEvent.VERTEX_ADDED, vertex);

        for (final VertexSetListener<V> l : vertexSetListeners) {
            l.vertexAdded(e);
        }

        for (final GraphListener<V, E> l : graphListeners) {
            l.vertexAdded(e);
        }
    }

    /**
     * Notify listeners that the specified vertex was removed.
     *
     * @param vertex the vertex that was removed.
     */
    protected void fireVertexRemoved(final V vertex) {

        final GraphVertexChangeEvent<V> e =
            createGraphVertexChangeEvent(GraphVertexChangeEvent.VERTEX_REMOVED, vertex);

        for (final VertexSetListener<V> l : vertexSetListeners) {
            l.vertexRemoved(e);
        }

        for (final GraphListener<V, E> l : graphListeners) {
            l.vertexRemoved(e);
        }
    }

    private static <L extends EventListener> void addToListenerList(final List<L> list, final L l) {

        if (!list.contains(l)) {
            list.add(l);
        }
    }

    private GraphEdgeChangeEvent<V, E> createGraphEdgeChangeEvent(final int eventType,
            final E edge) {

        if (reuseEvents) {

            reuseableEdgeEvent.setType(eventType);
            reuseableEdgeEvent.setEdge(edge);

            return reuseableEdgeEvent;

        } else {
            return new GraphEdgeChangeEvent<>(this, eventType, edge);
        }
    }

    private GraphVertexChangeEvent<V> createGraphVertexChangeEvent(final int eventType,
            final V vertex) {

        if (reuseEvents) {

            reuseableVertexEvent.setType(eventType);
            reuseableVertexEvent.setVertex(vertex);

            return reuseableVertexEvent;

        } else {
            return new GraphVertexChangeEvent<>(this, eventType, vertex);
        }
    }

    /**
     * A reuseable edge event.
     *
     * @author Barak Naveh
     * @since Aug 10, 2003
     * @param <VV>
     * @param <EE>
     */
    private static class FlyweightEdgeEvent<VV, EE> extends GraphEdgeChangeEvent<VV, EE> {

        private static final long serialVersionUID = 3907207152526636089L;

        /**
         * @see GraphEdgeChangeEvent#GraphEdgeChangeEvent(Object, int, Edge)
         */
        public FlyweightEdgeEvent(final Object eventSource, final int type, final EE e) {
            super(eventSource, type, e);
        }

        /**
         * Sets the edge of this event.
         *
         * @param e the edge to be set.
         */
        protected void setEdge(final EE e) {
            this.edge = e;
        }

        /**
         * Set the event type of this event.
         *
         * @param type the type to be set.
         */
        protected void setType(final int type) {
            this.type = type;
        }
    }

    /**
     * A reuseable vertex event.
     *
     * @author Barak Naveh
     * @since Aug 10, 2003
     * @param <VV>
     */
    private static class FlyweightVertexEvent<VV> extends GraphVertexChangeEvent<VV> {

        private static final long serialVersionUID = 3257848787857585716L;

        /**
         * @see GraphVertexChangeEvent#GraphVertexChangeEvent(Object, int,
         * Object)
         */
        public FlyweightVertexEvent(final Object eventSource, final int type, final VV vertex) {
            super(eventSource, type, vertex);
        }

        /**
         * Set the event type of this event.
         *
         * @param type type to be set.
         */
        protected void setType(final int type) {
            this.type = type;
        }

        /**
         * Sets the vertex of this event.
         *
         * @param vertex the vertex to be set.
         */
        protected void setVertex(final VV vertex) {
            this.vertex = vertex;
        }
    }
}

// End DefaultListenableGraph.java



